{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qFdPvlXBOdUN"
      },
      "source": [
        "# 고급 자동 미분"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/advanced_autodiff\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/advanced_autodiff.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/advanced_autodiff.ipynb\">     <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">    GitHub에서 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/advanced_autodiff.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8a859404ce7e"
      },
      "source": [
        "[자동 미분 가이드](autodiff.ipynb)에는 그래디언트를 계산하는 데 필요한 모든 것이 포함되어 있습니다. 이 가이드는 `tf.GradientTape` API의 더 깊고 덜 일반적인 기능에 중점을 둡니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MUXex9ctTuDB"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IqR2PQG4ZaZ0"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "import matplotlib as mpl\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "mpl.rcParams['figure.figsize'] = (8, 6)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uGRJJRi8TCkJ"
      },
      "source": [
        "## 그래디언트 기록 제어하기\n",
        "\n",
        "[자동 미분 가이드](autodiff.ipynb)에서는 그래디언트 계산을 빌드하는 동안 테이프에서 감시할 변수 및 텐서를 제어하는 방법을 살펴보았습니다.\n",
        "\n",
        "테이프에는 기록을 조작하는 방법도 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gB_i0VnhQKt2"
      },
      "source": [
        "그래디언트 기록을 중지하려면 `GradientTape.stop_recording()`을 사용하여 기록을 일시적으로 중단할 수 있습니다.\n",
        "\n",
        "모델 중간에서 복잡한 연산을 구별하지 않으려면, 일시 중단이 오버헤드를 줄이는 데 유용할 수 있습니다. 여기에는 메트릭 또는 중간 결과 계산이 포함될 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mhFSYf7uQWxR"
      },
      "outputs": [],
      "source": [
        "x = tf.Variable(2.0)\n",
        "y = tf.Variable(3.0)\n",
        "\n",
        "with tf.GradientTape() as t:\n",
        "  x_sq = x * x\n",
        "  with t.stop_recording():\n",
        "    y_sq = y * y\n",
        "  z = x_sq + y_sq\n",
        "\n",
        "grad = t.gradient(z, {'x': x, 'y': y})\n",
        "\n",
        "print('dz/dx:', grad['x'])  # 2*x => 4\n",
        "print('dz/dy:', grad['y'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DEHbEZ1h4p8A"
      },
      "source": [
        "완전히 다시 시작하려면, `reset()`을 사용합니다. 그래디언트 테이프 블록을 종료하고 다시 시작하는 것이 일반적으로 읽기 쉽지만, 테이프 블록을 종료하는 것이 어렵거나 불가능한 경우, `reset`을 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "lsMHsmrh4pqM"
      },
      "outputs": [],
      "source": [
        "x = tf.Variable(2.0)\n",
        "y = tf.Variable(3.0)\n",
        "reset = True\n",
        "\n",
        "with tf.GradientTape() as t:\n",
        "  y_sq = y * y\n",
        "  if reset:\n",
        "    # Throw out all the tape recorded so far\n",
        "    t.reset()\n",
        "  z = x * x + y_sq\n",
        "\n",
        "grad = t.gradient(z, {'x': x, 'y': y})\n",
        "\n",
        "print('dz/dx:', grad['x'])  # 2*x => 4\n",
        "print('dz/dy:', grad['y'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6zS7cLmS6zMf"
      },
      "source": [
        "## 그래디언트 중지\n",
        "\n",
        "위의 전역 테이프 컨트롤과 달리, `tf.stop_gradient` 함수는 훨씬 더 정확합니다. 테이프 자체에 액세스할 필요 없이 특정 경로를 따라 그래디언트가 흐르는 것을 막는 데 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "30qnZMe48BkB"
      },
      "outputs": [],
      "source": [
        "x = tf.Variable(2.0)\n",
        "y = tf.Variable(3.0)\n",
        "\n",
        "with tf.GradientTape() as t:\n",
        "  y_sq = y**2\n",
        "  z = x**2 + tf.stop_gradient(y_sq)\n",
        "\n",
        "grad = t.gradient(z, {'x': x, 'y': y})\n",
        "\n",
        "print('dz/dx:', grad['x'])  # 2*x => 4\n",
        "print('dz/dy:', grad['y'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mbb-9lnGVngH"
      },
      "source": [
        "## 사용자 정의 그래디언트\n",
        "\n",
        "경우에 따라 기본값을 사용하지 않고 그래디언트를 계산하는 방법을 정확하게 제어할 수 있습니다. 이러한 상황에는 다음이 포함됩니다.\n",
        "\n",
        "- 작성 중인 새 op에 대해 정의된 그래디언트가 없습니다.\n",
        "- 기본 계산이 수치적으로 불안정합니다.\n",
        "- 정방향 패스에서 값비싼 계산을 캐시하려고 합니다.\n",
        "- 그래디언트를 수정하지 않고 값(예 : `tf.clip_by_value` , `tf.math.round`)을 수정하려고 합니다.\n",
        "\n",
        "새 op를 작성하려면, `tf.RegisterGradient`를 사용하여 직접 설정할 수 있습니다. 자세한 내용은 해당 페이지를 참조하세요. (그래디언트 레지스트리는 전역이므로 주의해서 변경하세요.)\n",
        "\n",
        "후자의 세 가지 경우에는 `tf.custom_gradient`를 사용할 수 있습니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oHr31kc_irF_"
      },
      "source": [
        "다음은 `tf.clip_by_norm`을 중간 그래디언트에 적용하는 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Mjj01w4NYtwd"
      },
      "outputs": [],
      "source": [
        "# Establish an identity operation, but clip during the gradient pass\n",
        "@tf.custom_gradient\n",
        "def clip_gradients(y):\n",
        "  def backward(dy):\n",
        "    return tf.clip_by_norm(dy, 0.5)\n",
        "  return y, backward\n",
        "\n",
        "v = tf.Variable(2.0)\n",
        "with tf.GradientTape() as t:\n",
        "  output = clip_gradients(v * v)\n",
        "print(t.gradient(output, v))  # calls \"backward\", which clips 4 to 2\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n4t7S0scYrD3"
      },
      "source": [
        "자세한 내용은 `tf.custom_gradient`데코레이터를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8aENEt6Veryb"
      },
      "source": [
        "## 여러 테이프\n",
        "\n",
        "여러 테이프가 원활하게 상호 작용합니다. 예를 들어, 각 테이프는 서로 다른 텐서 세트를 감시합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "BJ0HdMvte0VZ"
      },
      "outputs": [],
      "source": [
        "x0 = tf.constant(0.0)\n",
        "x1 = tf.constant(0.0)\n",
        "\n",
        "with tf.GradientTape() as tape0, tf.GradientTape() as tape1:\n",
        "  tape0.watch(x0)\n",
        "  tape1.watch(x1)\n",
        "\n",
        "  y0 = tf.math.sin(x0)\n",
        "  y1 = tf.nn.sigmoid(x1)\n",
        "\n",
        "  y = y0 + y1\n",
        "\n",
        "  ys = tf.reduce_sum(y)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6ApAoMNFfNz6"
      },
      "outputs": [],
      "source": [
        "tape0.gradient(ys, x0).numpy()   # cos(x) => 1.0"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rF1jrAJsfYW_"
      },
      "outputs": [],
      "source": [
        "tape1.gradient(ys, x1).numpy()   # sigmoid(x1)*(1-sigmoid(x1)) => 0.25"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DK05KXrAAld3"
      },
      "source": [
        "### 고계도 그래디언트\n",
        "\n",
        "`GradientTape` 컨텍스트 관리자 내부의 연산은 자동 미분을 위해 기록됩니다. 해당 컨텍스트에서 그래디언트가 계산되면, 그래디언트 계산도 기록됩니다. 결과적으로, 정확히 같은 API가 고계도 그래디언트에도 작동합니다. 예를 들면, 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cPQgthZ7ugRJ"
      },
      "outputs": [],
      "source": [
        "x = tf.Variable(1.0)  # Create a Tensorflow variable initialized to 1.0\n",
        "\n",
        "with tf.GradientTape() as t2:\n",
        "  with tf.GradientTape() as t1:\n",
        "    y = x * x * x\n",
        "\n",
        "  # Compute the gradient inside the outer `t2` context manager\n",
        "  # which means the gradient computation is differentiable as well.\n",
        "  dy_dx = t1.gradient(y, x)\n",
        "d2y_dx2 = t2.gradient(dy_dx, x)\n",
        "\n",
        "print('dy_dx:', dy_dx.numpy())  # 3 * x**2 => 3.0\n",
        "print('d2y_dx2:', d2y_dx2.numpy())  # 6 * x => 6.0"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "k0HV-Ah4_76i"
      },
      "source": [
        "*스칼라* 함수의 2차 미분을 제공하지만, `GradientTape.gradient`는 스칼라의 그래디언트만 계산하므로 이 패턴은 Hessian 행렬을 생성하도록 일반화되지 않습니다. Hessian을 구성하려면 [야고비안 섹션](#jacobians)의 [Hessian 예제](#hessian)를 참조하세요.\n",
        "\n",
        "그래디언트에서 스칼라를 계산할 때 \"`GradientTape.gradient`에 대한 중첩된 호출\"은 좋은 패턴이며, 결과 스칼라는 다음 예제와 같이 두 번째 그래디언트 계산의 소스로 작동합니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "t7LRlcpVKHv1"
      },
      "source": [
        "#### 예: 입력 그래디언트 정규화\n",
        "\n",
        "많은 모델이 \"적대적인 예\"에 취약합니다. 이 기술 컬렉션은 모델의 입력을 수정하여 모델의 출력이 혼동됩니다. [가장 간단한 구현](https://www.tensorflow.org/tutorials/generative/adversarial_fgsm)은 입력에 대한 출력의 그래디언트를 따라 단일 단계를 수행합니다. \"입력 그래디언트\".\n",
        "\n",
        "적대적인 예에 ​​대한 견고성을 높이는 한 가지 기술은 [입력 그래디언트 정규화](https://arxiv.org/abs/1905.11468)로, 입력 그래디언트의 크기를 최소화하려고 시도합니다. 입력 그래디언트가 작으면 출력의 변화도 작아야 합니다.\n",
        "\n",
        "아래는 입력 그래디언트 정규화의 네이티브 구현입니다. 구현은 다음과 같습니다.\n",
        "\n",
        "1. 내부 테이프를 사용하여 입력에 대한 출력 그래디언트를 계산합니다.\n",
        "2. 해당 입력 그래디언트의 크기를 계산합니다.\n",
        "3. 모델에 대한 해당 크기의 그래디언트를 계산합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tH3ZFuUfDLrR"
      },
      "outputs": [],
      "source": [
        "x = tf.random.normal([7, 5])\n",
        "\n",
        "layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "E6yOFsjEDR9u"
      },
      "outputs": [],
      "source": [
        "with tf.GradientTape() as t2:\n",
        "  # The inner tape only takes the gradient with respect to the input,\n",
        "  # not the variables.\n",
        "  with tf.GradientTape(watch_accessed_variables=False) as t1:\n",
        "    t1.watch(x)\n",
        "    y = layer(x)\n",
        "    out = tf.reduce_sum(layer(x)**2)\n",
        "  # 1. Calculate the input gradient.\n",
        "  g1 = t1.gradient(out, x)\n",
        "  # 2. Calculate the magnitude of the input gradient.\n",
        "  g1_mag = tf.norm(g1)\n",
        "\n",
        "# 3. Calculate the gradient of the magnitude with respect to the model.\n",
        "dg1_mag = t2.gradient(g1_mag, layer.trainable_variables)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "123QMq6PqK_d"
      },
      "outputs": [],
      "source": [
        "[var.shape for var in dg1_mag]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "E4xiYigexMtQ"
      },
      "source": [
        "## 야고비안\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4-hVHVIeExkI"
      },
      "source": [
        "이전의 모든 예제는 일부 소스 텐서와 관련하여 스칼라 대상의 그래디언트를 나타냅니다.\n",
        "\n",
        "[야고비 행렬식](https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant)은 벡터값 함수의 그래디언트를 나타냅니다. 각 행에는 벡터 요소 중 하나의 그래디언트가 포함됩니다.\n",
        "\n",
        "`GradientTape.jacobian` 메서드를 사용하면 야고비 행렬식을 효율적으로 계산할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KzNyIM0QBYIH"
      },
      "source": [
        "참고:\n",
        "\n",
        "- `gradient`처럼: `sources` 인수는 텐서 또는 텐서의 컨테이너가 될 수 있습니다.\n",
        "- `gradient`와 달리: `target` 텐서는 단일 텐서여야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O74K3hlxBC8a"
      },
      "source": [
        "### 스칼라 소스"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B08OKn1Orkuc"
      },
      "source": [
        "첫 번째 예는, 스칼라 소스에 대한 벡터 대상의 야코비안입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bAFeIE8EuVIq"
      },
      "outputs": [],
      "source": [
        "x = tf.linspace(-10.0, 10.0, 200+1)\n",
        "delta = tf.Variable(0.0)\n",
        "\n",
        "with tf.GradientTape() as tape:\n",
        "  y = tf.nn.sigmoid(x+delta)\n",
        "\n",
        "dy_dx = tape.jacobian(y, delta)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BgHbUk3zr-WU"
      },
      "source": [
        "스칼라에 대한 야고비안을 취하면, 결과는 **대상**의 형상을 가지며 소스에 대한 각 요소의 그래디언트를 제공합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iZ6awnDzr_BA"
      },
      "outputs": [],
      "source": [
        "print(y.shape)\n",
        "print(dy_dx.shape)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "siNZaklc0_-e"
      },
      "outputs": [],
      "source": [
        "plt.plot(x.numpy(), y, label='y')\n",
        "plt.plot(x.numpy(), dy_dx, label='dy/dx')\n",
        "plt.legend()\n",
        "_ = plt.xlabel('x')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DsOMSD_1BGkD"
      },
      "source": [
        "### 텐서 소스"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g3iXKN7KF-st"
      },
      "source": [
        "입력이 스칼라이든 텐서이든 `GradientTape.jacobian`은 대상(들)의 각 요소에 대한 각 소스 요소의 그래디언트를 효율적으로 계산합니다.\n",
        "\n",
        "예를 들어, 이 레이어의 출력은 형상 `(10, 7)`입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "39YXItgLxMBk"
      },
      "outputs": [],
      "source": [
        "x = tf.random.normal([7, 5])\n",
        "layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)\n",
        "\n",
        "with tf.GradientTape(persistent=True) as tape:\n",
        "  y = layer(x)\n",
        "\n",
        "y.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tshNRtfKuVP_"
      },
      "source": [
        "레이어의 커널 형상은 `(5, 10)`입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CigTWyfPvPuv"
      },
      "outputs": [],
      "source": [
        "layer.kernel.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mN96JRpnAjpx"
      },
      "source": [
        "커널에 대한 출력의 야고비안 형상은 서로 연결된 두 가지 형상입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pRLzTTbvEimH"
      },
      "outputs": [],
      "source": [
        "j = tape.jacobian(y, layer.kernel)\n",
        "j.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2Lrv7miMvTll"
      },
      "source": [
        "대상의 차원을 합하면, `GradientTape.gradient`에서 계산한 합계의 그래디언트가 남습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FJjZpYRnDjVa"
      },
      "outputs": [],
      "source": [
        "g = tape.gradient(y, layer.kernel)\n",
        "print('g.shape:', g.shape)\n",
        "\n",
        "j_sum = tf.reduce_sum(j, axis=[0, 1])\n",
        "delta = tf.reduce_max(abs(g - j_sum)).numpy()\n",
        "assert delta < 1e-3\n",
        "print('delta:', delta)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZKajuGlk_krs"
      },
      "source": [
        "<a id=\"hessian\"> </a>\n",
        "\n",
        "#### 예: Hessian"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NYcsXeo8TDLi"
      },
      "source": [
        "`tf.GradientTape`는 Hessian 행렬을 구성하기 위한 명시적인 방법을 제공하지 않지만, `GradientTape.jacobian` 메서드를 사용하여 빌드할 수 있습니다.\n",
        "\n",
        "참고: Hessian 행렬은 `N **2` 매개변수를 포함합니다. 그 외 여러 이유로 인해 대부분의 모델에는 실용적이지 않습니다. 이 예제는 `GradientTape.jacobian` 메서드를 사용하는 방법에 대한 설명으로 포함되어 있으며 직접적인 Hessian 기반 최적화를 보증하는 것은 아닙니다. Hessian-vector 곱은 [중첩 테이프를 사용하여 효율적으로 계산](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/eager/benchmarks/resnet50/hvp_test.py)할 수 있으며 2차 최적화에 대한 훨씬 효율적인 접근 방식입니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ELGTaell_j81"
      },
      "outputs": [],
      "source": [
        "x = tf.random.normal([7, 5])\n",
        "layer1 = tf.keras.layers.Dense(8, activation=tf.nn.relu)\n",
        "layer2 = tf.keras.layers.Dense(6, activation=tf.nn.relu)\n",
        "\n",
        "with tf.GradientTape() as t2:\n",
        "  with tf.GradientTape() as t1:\n",
        "    x = layer1(x)\n",
        "    x = layer2(x)\n",
        "    loss = tf.reduce_mean(x**2)\n",
        "\n",
        "  g = t1.gradient(loss, layer1.kernel)\n",
        "\n",
        "h = t2.jacobian(g, layer1.kernel)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FVqQuZj4XGjm"
      },
      "outputs": [],
      "source": [
        "print(f'layer.kernel.shape: {layer1.kernel.shape}')\n",
        "print(f'h.shape: {h.shape}')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_M7XElgaiMeP"
      },
      "source": [
        "이 Hessian을 뉴턴의 방법 단계에 사용하려면, 먼저 축을 행렬로 평면화하고 그래디언트를 벡터로 평면화합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6te7N6wVXwXX"
      },
      "outputs": [],
      "source": [
        "n_params = tf.reduce_prod(layer1.kernel.shape)\n",
        "\n",
        "g_vec = tf.reshape(g, [n_params, 1])\n",
        "h_mat = tf.reshape(h, [n_params, n_params])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L9rO8b-0mgOH"
      },
      "source": [
        "Hessian 행렬은 대칭이어야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8TCHc7Vrf52S"
      },
      "outputs": [],
      "source": [
        "def imshow_zero_center(image, **kwargs):\n",
        "  lim = tf.reduce_max(abs(image))\n",
        "  plt.imshow(image, vmin=-lim, vmax=lim, cmap='seismic', **kwargs)\n",
        "  plt.colorbar()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DExOxd7Ok2H0"
      },
      "outputs": [],
      "source": [
        "imshow_zero_center(h_mat)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "13fBswmtQes4"
      },
      "source": [
        "뉴턴의 방법 업데이트 단계는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3DdnbynBdSor"
      },
      "outputs": [],
      "source": [
        "eps = 1e-3\n",
        "eye_eps = tf.eye(h_mat.shape[0])*eps"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-zPdtyoWeUeV"
      },
      "source": [
        "참고: [실제로 행렬을 반전하지 마세요](https://www.johndcook.com/blog/2010/01/19/dont-invert-that-matrix/)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "k1LYftgmswOO"
      },
      "outputs": [],
      "source": [
        "# X(k+1) = X(k) - (∇²f(X(k)))^-1 @ ∇f(X(k))\n",
        "# h_mat = ∇²f(X(k))\n",
        "# g_vec = ∇f(X(k))\n",
        "update = tf.linalg.solve(h_mat + eye_eps, g_vec)\n",
        "\n",
        "# Reshape the update and apply it to the variable.\n",
        "_ = layer1.kernel.assign_sub(tf.reshape(update, layer1.kernel.shape))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pF6qjlHKWxF4"
      },
      "source": [
        "단일 `tf.Variable` 경우에는 비교적 간단하지만, 사소하지 않은 모델에 적용하려면 여러 변수에 걸쳐 완전한 Hessian을 생성하기 위해 신중하게 연결하고 슬라이스해야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PQWM0uN-GO5t"
      },
      "source": [
        "### 배치 야고비안"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hKtB3rY6EySJ"
      },
      "source": [
        "어떤 경우에는 소스 스택과 관련하여 각 대상 스택의 야고비안을 가져오려 합니다. 여기에서 각 대상-소스 쌍의 야고비안은 독립적입니다.\n",
        "\n",
        "예를 들어, 여기에서 입력 `x`는 `(batch, ins)` 형상이 되고, 출력 `y`는 `(batch, outs)` 형상이 됩니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tQMndhIUHMes"
      },
      "outputs": [],
      "source": [
        "x = tf.random.normal([7, 5])\n",
        "\n",
        "layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)\n",
        "layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)\n",
        "\n",
        "with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:\n",
        "  tape.watch(x)\n",
        "  y = layer1(x)\n",
        "  y = layer2(y)\n",
        "\n",
        "y.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ff2spRHEJXBU"
      },
      "source": [
        "`x`에 대한 `y`의 전체 야고비안은 `(batch, ins, outs)`만 원하는 경우에도 `(batch, ins, batch, outs)`의 형상을 가집니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1zSl2A5-HhMH"
      },
      "outputs": [],
      "source": [
        "j = tape.jacobian(y, x)\n",
        "j.shape"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UibJijPLJrpQ"
      },
      "source": [
        "스택에 있는 각 항목의 그래디언트가 독립적이면, 이 텐서의 모든 `(batch, batch)` 슬라이스는 대각선 행렬입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZFl9uj3ueVSH"
      },
      "outputs": [],
      "source": [
        "imshow_zero_center(j[:, 0, :, 0])\n",
        "_ = plt.title('A (batch, batch) slice')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "g4ZoRJcJNmy5"
      },
      "outputs": [],
      "source": [
        "def plot_as_patches(j):\n",
        "  # Reorder axes so the diagonals will each form a contiguous patch.\n",
        "  j = tf.transpose(j, [1, 0, 3, 2])\n",
        "  # Pad in between each patch.\n",
        "  lim = tf.reduce_max(abs(j))\n",
        "  j = tf.pad(j, [[0, 0], [1, 1], [0, 0], [1, 1]],\n",
        "             constant_values=-lim)\n",
        "  # Reshape to form a single image.\n",
        "  s = j.shape\n",
        "  j = tf.reshape(j, [s[0]*s[1], s[2]*s[3]])\n",
        "  imshow_zero_center(j, extent=[-0.5, s[2]-0.5, s[0]-0.5, -0.5])\n",
        "\n",
        "plot_as_patches(j)\n",
        "_ = plt.title('All (batch, batch) slices are diagonal')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OXpTBKyeK84z"
      },
      "source": [
        "원하는 결과를 얻으려면 중복 `batch` 차원를 합산하거나 `tf.einsum`을 사용하여 대각선을 선택할 수 있습니다.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v65OAjEgLQwl"
      },
      "outputs": [],
      "source": [
        "j_sum = tf.reduce_sum(j, axis=2)\n",
        "print(j_sum.shape)\n",
        "j_select = tf.einsum('bxby->bxy', j)\n",
        "print(j_select.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zT_VfR6lcwxD"
      },
      "source": [
        "처음부터 추가 차원 없이 계산을 수행하는 것이 훨씬 더 효율적입니다. `GradientTape.batch_jacobian` 메서드는 정확히 그렇게 합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YJLIl9WpHqYq"
      },
      "outputs": [],
      "source": [
        "jb = tape.batch_jacobian(y, x)\n",
        "jb.shape"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-5t_q5SfHw7T"
      },
      "outputs": [],
      "source": [
        "error = tf.reduce_max(abs(jb - j_sum))\n",
        "assert error < 1e-3\n",
        "print(error.numpy())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IUeY2ZCiL31I"
      },
      "source": [
        "주의: `GradientTape.batch_jacobian`은 소스 및 대상의 첫 번째 차원만 일치하는지 확인합니다. 그래디언트가 실제로 독립적인지 확인하지 않습니다. 적합한 경우에 `batch_jacobian`만을 사용하도록 하는 것은 사용자에게 달려 있습니다. 예를 들어, `layers.BatchNormalization`를 추가하면 `batch` 차원에서 정규화되므로 독립성이 없어집니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tnDugVc-L4fj"
      },
      "outputs": [],
      "source": [
        "x = tf.random.normal([7, 5])\n",
        "\n",
        "layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)\n",
        "bn = tf.keras.layers.BatchNormalization()\n",
        "layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)\n",
        "\n",
        "with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:\n",
        "  tape.watch(x)\n",
        "  y = layer1(x)\n",
        "  y = bn(y, training=True)\n",
        "  y = layer2(y)\n",
        "\n",
        "j = tape.jacobian(y, x)\n",
        "print(f'j.shape: {j.shape}')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "SNyZ1WhJMVLm"
      },
      "outputs": [],
      "source": [
        "plot_as_patches(j)\n",
        "\n",
        "_ = plt.title('These slices are not diagonal')\n",
        "_ = plt.xlabel(\"Don't use `batch_jacobian`\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M_x7ih5sarvG"
      },
      "source": [
        "이 경우, `batch_jacobian`가 여전히 실행되어 예상 형상을 가진 *어떤 것*을 반환하지만, 내용은 명확하지 않습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "k8_mICHoasCi"
      },
      "outputs": [],
      "source": [
        "jb = tape.batch_jacobian(y, x)\n",
        "print(f'jb.shape: {jb.shape}')"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "Tce3stUlHN0L"
      ],
      "name": "advanced_autodiff.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
